
#include <math.h>
#include <algorithm>

#include "Tween.h"


namespace tween {


	Action::Action(ITarget* target, float startTime, float duration)
		:next(nullptr), prev(nullptr), ease(nullptr), inited(false)
	{
		this->target = target;
		this->startTime = startTime;
		this->duration = duration;
		this->endTime = startTime + duration;
	}

	Action::~Action() {

	}

	void Action::setPosition(float position, bool isReverse) {
		float ratio = this->duration == 0 ? 1 : (position - this->startTime) / this->duration;
		if (ratio < 0) {
			ratio = 0;
		}
		else if (ratio > 1) {
			ratio = 1;
		}
		if (this->ease) {
			ratio = this->ease(ratio);
		}
		if (!this->inited) {
			this->inited = true;
			this->init(ratio, isReverse);
		}
		this->update(ratio, isReverse);
	}

	void Action::init(float ratio, bool isReverse) {
	}
	void Action::update(float ratio, bool isReverse) {
	}


	// class RelativeNum
	RelativeNum::RelativeNum(ITarget* target, float startTime, float duration, float deltaValue, const std::string& propName)
		:Action(target, startTime, duration), deltaValue(deltaValue), propName(propName), lastDeltaValue(0)
	{
	}

	RelativeNum::~RelativeNum()	{

	}
	void RelativeNum::update(float ratio, bool isReverse) {
		float currDeltaValue = ratio * this->deltaValue;
		float delta = currDeltaValue - this->lastDeltaValue;
		this->lastDeltaValue = currDeltaValue;

		float curr = this->target->getNumber(this->propName);
		this->target->setNumber(this->propName, curr + delta);
	}



	// class AbsoluteNum
	AbsoluteNum::AbsoluteNum(ITarget* target, float startTime, float duration, float  toValue, const std::string& propName)
		:Action(target, startTime, duration), toValue(toValue), propName(propName), startValue(0), deltaValue(0)
	{
	}
	AbsoluteNum::~AbsoluteNum() {
	}
	void AbsoluteNum::init(float ratio, bool isReverse) {
		this->startValue = this->target->getNumber(this->propName);
		this->deltaValue = this->toValue - this->startValue;
	}
	void AbsoluteNum::update(float ratio, bool isReverse) {
		this->target->setNumber(this->propName, this->startValue + ratio * this->deltaValue);
	}

	// class StepAction
	StepAction::StepAction(ITarget* target, float startTime, float duration, const std::vector<void*>& steps, const std::string& propName)
		:Action(target, startTime, duration), steps(steps), propName(propName), stepIndex(0)
	{
		this->stepLength = this->steps.size();
	}

	StepAction::~StepAction() {

	}
	void StepAction::update(float ratio, bool isReverse) {
		if (ratio < 0) {
			ratio = 0;
		}
		else if (ratio > 1) {
			ratio = 1;
		}
		int stepIndex = (int)ceilf(ratio * this->stepLength) - 1;
		if (this->stepIndex == stepIndex) {
			return;
		}
		this->stepIndex = stepIndex;

		this->target->setPointer(this->propName, this->steps[stepIndex]);
	}

	// helper function
	static inline Action* creatAction(ITarget* target, const ActionData& frame) {
		if (frame.type == ActionType::None) {
			return nullptr;
		}
		else
		{
			Action* action = nullptr;
			if (frame.type == ActionType::RelNum)
			{
				float v = frame.v.num;
				action = new RelativeNum(target, frame.t, frame.dur, v, frame.propName);
			}
			else if (frame.type == ActionType::AbsNum)
			{
				float v = frame.v.num;
				action = new AbsoluteNum(target, frame.t, frame.dur, v, frame.propName);
			}
			else if (frame.type == ActionType::Step)
			{
				std::vector<void*>* vp = ((std::vector<void*>*)frame.v.ptr);
				action = new StepAction(target, frame.t, frame.dur, *vp, frame.propName);
			}
			if (action) {
				action->ease = frame.ease;
			}
			return action;
		}
		return nullptr;
	}


	// class Tween
	const std::string Tween::Complete = "complete";
	const long Tween::isInTick = 0;

	Tween::Tween(ITarget* target, std::vector<ActionData>& frames, TweenOptions& options) {

		if (!target) {
			throw "target 没有值!!!";
		}
		
		this->target = target;

		this->rawPosition = 0;
		this->lastTick = 0;

		this->loop = options.loop < 0 ? -1 : (options.loop || 0);
		this->useTicks = options.useTicks;
		this->yoyo = options.yoyo;
		this->timeScale = options.timeScale || 1;
		
		this->prevTime = 0;
		this->_paused = false;
		this->actionHead = nullptr;
		this->actionTail = nullptr;

		if (frames.size() == 0) {
			throw "frames 没有数据!!!";
		}
		this->duration = this->initActions(frames);
	}

	Tween::~Tween() {
		Action* action = this->actionHead;
		Action* next = nullptr;
		while (action) {
			next = action->next;
			delete action;
			action = next;
		}
	}

	float Tween::initActions(std::vector<ActionData>& frames) {
		std::sort(std::begin(frames), std::end(frames), [](ActionData& a, ActionData& b) {// 无需稳定排序 快排即可
			return a.t < b.t;
		});

		float duration = 0;
		for (ActionData& frame : frames) {
			Action* action = creatAction(this->target,frame);
			if (!action) {
				continue;
			}
			Action* actionTail = this->actionTail;
			if (!actionTail) {
				this->actionHead = this->actionTail = action;
			}
			else {
				this->actionTail = actionTail->next = action;
				action->prev = actionTail;
			}
			if (action->endTime > duration) {
				duration = action->endTime;
			}
		}
		 
		return duration;
	}


	void Tween::advance(float delta){
		this->setPosition(this->rawPosition + delta * this->timeScale);
	}

	void Tween::setPosition(float rawPosition) {
		const float d = this->duration;
		const int loopCount = this->loop;
		const float prevRawPos = this->rawPosition;
		int loop = 0;
		float position = 0;
		bool end = false;

		if (d == 0) {// deal with 0 length tweens.

			Action* action = this->actionHead;
			Action* next = nullptr;
			while (action) {
				next = action->next;
				action->setPosition(position, false);
				action = next;
			}
			this->setPaused(true);
			this->dispatchEvent(Tween::Complete);
			return;
		}
		else {

			loop = (int)floorf(rawPosition / d);// 向下取整
			position = rawPosition - loop * d;

			end = (loopCount != -1 && rawPosition >= loopCount * d + d);
			if (end) {
				rawPosition = (position = d) * (loop = loopCount) + d;
			}
			if (rawPosition == prevRawPos) {
				return;// no need to update
			}

			if (this->yoyo && (loop % 2 != 0)) {// current loop is reversed
				position = d - position;
			}
		}
		this->rawPosition = rawPosition;

		const float prevTime = this->prevTime;
		this->prevTime = position;
		if (prevTime == position) {
			return;
		}

		if (position > prevTime) {

			Action* action = this->actionHead;
			Action* next = nullptr;
			while (action) {
				next = action->next;
				if (action->endTime < prevTime || action->startTime > position) {
					action = next;
					continue;
				}
				action->setPosition(position, false);
				action = next;
			}
		}
		else {// reverse

			Action* action = this->actionTail;
			Action* prev = nullptr;
			while (action) {
				prev = action->prev;
				if (action->endTime < position || action->startTime > prevTime) {
					action = prev;
					continue;
				}
				action->setPosition(position, true);
				action = prev;
			}
		}

		if (end) {
			this->setPaused(true);
			this->dispatchEvent(Tween::Complete);
		}
	}


	void Tween::setPaused(bool value) {
		this->_paused = value;
	}
	bool Tween::getPaused() {
		return this->_paused;
	}
	void Tween::dispatchEvent(const std::string& eventName) {
	}


}// namespace tween 
